package master;

import java.awt.*;
import javax.swing.*;

/**
 * Class for handling complex numbers in the quantum computing project.
 * For any alterations to the coefficients, please use the getter and setter methods 
 * instead of altering the variables directly
 * @author Luke Dobinson
 *
 */

public class ComplexNumber{
	
	protected float re, im;
	
	
	/******************************
	 * Added stuff
	 * @return
	 */
	
	/**
	 * return the magnitude of this number as a float value
	 */
	public float magnitudeFloat(){
        return (float)Math.hypot(getReal(), getImaginary());
	}
	
	/**
	 * return the phase of this number as a float value
	 */
	public float phaseFloat(){
        float r = Math.abs(getReal()),i = Math.abs(getImaginary());
        float phase = (float)(Math.atan(i/r));
        
        if(getReal() < 0)phase = (float)Math.PI - phase;
        if(getImaginary() < 0) phase = (2*(float)Math.PI) -phase;
        
        return phase;
	}
	
	/**
	 * default constructor (I personally don't like this very much but whatever :D)
	 */
	public ComplexNumber(){
        setReal(0);
        setImaginary(0);
	}
	
	/**
	 * scale the complex number by a float value. I reccomend renaming this method to scale
	 * @param a
	 * @return
	 */
	public ComplexNumber multByNo(float a){
        setReal(getReal()*a);
        setImaginary(getImaginary()*a);
        return this;
	}
	
	
	
	/**************************************
	 * 
	 */
	
	
	/**
	 * Default method for getting the real value of the complex number, may be overridden
	 */
	public float getReal(){return re;}
	
	/**
	 * Default method for getting the complex value of the complex number, may be overridden
	 */
	public float getImaginary(){return im;}
	
	/**
	 * Return the magnitude of the complex number, as a complex number
	 * @return
	 */
	public ComplexNumber magnitude(){
		return new ComplexNumber((float)Math.hypot(getReal(), getImaginary()),0);
	}
	/**
	 * Return the phase of the complex number, as a complex number
	 * @return
	 */
	public ComplexNumber phase(){
		
		if(getReal()==0 && getImaginary()==0)return new ComplexNumber(0,0);
		
		float r = Math.abs(getReal()),i = Math.abs(getImaginary());
		float phase = (float)(Math.atan(i/r));
		
		if(getReal() < 0)phase = (float)Math.PI - phase;
		if(getImaginary() < 0) phase = (2*(float)Math.PI) -phase;
		
		return new ComplexNumber( phase ,0);
	}
	/**
	 * return the conjugate of the complex number as a complex number
	 * @return
	 */
	public ComplexNumber conjugate(){
		return new ComplexNumber(getReal(),-getImaginary());
	}
	
	/**
	 * Set the magnitude of the complex number
	 * @param m
	 */
	public void setMagnitude(float m){
		float mag = this.magnitude().getReal();
		float r = this.getReal();
		float i = this.getImaginary();
		System.out.println(r + "  " + i);
		r = r/mag; r *= m;
		i = i / mag; i *=m;
		
		setReal(r);
		setImaginary(i);
	}
	
	/**
	 * static method that returns a new complex number as a function of its phase and magnitude
	 * @param magnitude
	 * @param theta
	 * @return
	 */
	public static ComplexNumber expi(float magnitude, float theta){
		return new ComplexNumber(
				(float) (magnitude*Math.cos(theta)),
				(float) (magnitude*Math.sin(theta))
				)
				;
	}
	
	/**
	 * Assign the real value of the complex number directly
	 * @param re
	 */
	public void setReal(float re){
		this.re = re;
	}
	
	/**
	 * assign the complex value of the complex number directly
	 * @param im
	 */
	public void setImaginary(float im){
		this.im = im;
	}
	
	/**
	 * outdated method from when Complex Numbers implemented a field interface
	 * @param b
	 * @return
	 */
	private ComplexNumber compatibilityCast(ComplexNumber b){
		if ( b instanceof ComplexNumber) return (ComplexNumber)b;
		throw new IllegalArgumentException("Not Compatible!");
	}
	
	/**
	 * Constructor for specifying the real and complex parts directly
	 * @param re
	 * @param im
	 */
	public ComplexNumber(float re, float im){
		setReal(re);
		setImaginary(im);
	}
	
	/**
	 * Method that adds a complex number to this number
	 * @param b
	 * @return
	 */
	public ComplexNumber add(ComplexNumber b) {
		ComplexNumber op = compatibilityCast(b);
		this.re += op.getReal();
		this.im += op.getImaginary();
		return this;
	}
	
	/**
	 * Method that subtracts a complex number from this number
	 * @param b
	 * @return
	 */
	public ComplexNumber subtract(ComplexNumber b) {
		ComplexNumber op = compatibilityCast(b);
		this.re -= op.getReal();
		this.im -= op.getImaginary();
		return this;
	}
	
	
	/**
	 * Method that multiplies this complex number by another
	 * @param f
	 * @return
	 */
	public ComplexNumber multiply(ComplexNumber f) {
		ComplexNumber op = compatibilityCast(f);
		float a = this.getReal() ,
			b = this.getImaginary() ,
			c = op.getReal() ,
			d = op.getImaginary();
		this.re = a*c - b*d;
		this.im = a*d +b*c;
		return this;
	}
	
	/**
	 * Method that divides this complex number by another
	 * @param f
	 * @return
	 */
	public ComplexNumber divide(ComplexNumber f) {
		ComplexNumber op = compatibilityCast(f.createNewInstance());
		return this.multiply(op.invert());
	}
	
	/**
	 * Method that inverts this number
	 * @return
	 */
	public ComplexNumber invert() {
		float a = this.getReal() , b = this.getImaginary();
		setReal(  a / (a*a + b*b));
		setImaginary(  - b /(a*a + b*b));
		return this;
	}
	
	
	/**
	 * Equivalent to multiplying this number by -1
	 * @return
	 */
	public ComplexNumber negate() {
		setReal( -getReal());
		setImaginary( -getImaginary());
		return this;
	}
	
	/**
	 * create a copy of this object that can be modified without affecting this instance
	 * @return
	 */
	public ComplexNumber createNewInstance() {
		return new ComplexNumber(getReal(),getImaginary());
	}

	/**
	 * Set the value of this complex number to zero
	 * @return
	 */
	public ComplexNumber setZero() {
		setReal(0);
		setImaginary(0);
		return this;
	}
	
	/**
	 * returns the test condition for this number being equal to zero
	 * @return
	 */
	public boolean isZero() {
		return (getReal() == 0 && getImaginary() == 0);
	}
	
	/**
	 * sets this number to unity (1+0i)
	 * @return
	 */
	public ComplexNumber setUnity() {
		setReal(1f);
		setImaginary(0);
		return this;
	}
	
	/**
	 * returns whether this number is equivalent to unity
	 * @return
	 */
	public boolean isUnity() {
		return (getReal() == 1 && getImaginary() == 0);
	}

	/**
	 * returns whether this number is equivalent to another
	 * @param b
	 * @return
	 */
	public boolean equals(ComplexNumber b) {
		ComplexNumber op = compatibilityCast(b);
		return (getReal() == op.getReal() && getImaginary() == op.getImaginary());
	}
	
	/**
	 * Returns this number in a readable string format
	 */
	public String toString(){
		if (isZero()) return "0";
		String val = "";
		if(getReal() != 0)val += getReal();
		if(getImaginary() != 0){
			val += " ";
			if(getImaginary() > 0 && getReal()!=0) val += "+";
			val+=getImaginary();
			val+="i";
		}
		return val;
	}
	
	/**
	 * Draw the number as a vector on a graphics canvass
	 * @param g the graphics object
	 * @param x the xcoordinate to offset to
	 * @param y the ycoordinate to offset to
	 * @param multiplier the scale of the number a scale of 1 means a
	 *  value of 1 corresponds to 1 px etc, reccomend scale roughly ~ 100
	 */
	public void draw(Graphics g, int x, int y, float multiplier){
		int drawX =  x + (int)(getReal()*multiplier);
		int drawY = y - (int)(getImaginary()*multiplier);
		g.drawLine(x, y, drawX, drawY);
		g.drawOval(drawX-5, drawY-5, 10, 10);
		//draw line
	}
	
	/**
	 * Opens a new JFrame with ComplexNumber panels displaying the numbers in the array
	 * @param title
	 * @param horizontal
	 * @param editable
	 * @param numbers
	 */
	public static void display(String title,boolean horizontal , boolean editable, ComplexNumber ... numbers){
		JFrame myFrame = new JFrame();
		myFrame.setTitle(title);
		int defaultDimension = 200;
		if(horizontal){
			myFrame.setLayout(new GridLayout(1,numbers.length));
			myFrame.setSize(defaultDimension*numbers.length,defaultDimension);
		}
		else{
			myFrame.setLayout(new GridLayout(numbers.length,1));
			myFrame.setSize(defaultDimension,defaultDimension*numbers.length);
		}
		for(int i = 0 ; i < numbers.length ; i++){
			myFrame.getContentPane().add(
					new ComplexNumberPanel(myFrame,numbers[i],75f,editable)
			);
		}
		
		myFrame.setVisible(true);
	}
	
	/**
	 * Return a new ComplexNumber panel with a default scale value of 100
	 * @param defaultDimension
	 * @param horizontal
	 * @param editable
	 * @param numbers
	 * @return
	 */
	public static JPanel display(int defaultDimension,boolean horizontal , boolean editable, ComplexNumber ... numbers){
		return display(100f, defaultDimension,  horizontal ,editable, numbers);
	}
	
	/**
	 * return a new ComplexNumber panel with a scale value specified by the user
	 * @param scale
	 * @param defaultDimension
	 * @param horizontal
	 * @param editable
	 * @param numbers
	 * @return
	 */
	public static JPanel display(float scale,int defaultDimension,boolean horizontal , boolean editable, ComplexNumber ... numbers){
		JPanel mainPanel = new JPanel();
		if(horizontal){
			mainPanel.setLayout(new GridLayout(1,numbers.length));
			mainPanel.setSize(defaultDimension*numbers.length,defaultDimension);
		}
		else{
			mainPanel.setLayout(new GridLayout(numbers.length,1));
			mainPanel.setPreferredSize(new Dimension(defaultDimension,defaultDimension*numbers.length));
		}
		for(int i = 0 ; i < numbers.length ; i++){
			mainPanel.add(
					new ComplexNumberPanel(mainPanel,numbers[i],scale,editable)
			);
		}
		
		return mainPanel;
	}
	
	/**
	 * parse a new complex number from a string of the format "real, imaginary"
	 * @param t
	 * @return
	 */
	public static ComplexNumber parse(String t){
		String [] data = t.split(",");
		return new ComplexNumber((float)Double.parseDouble(data[0]),(float)Double.parseDouble(data[1]));
	}
	
	/**
	 * parse an array of compelx numbers from a string of numbers seperated by newlines
	 * @param t
	 * @return
	 */
	public static ComplexNumber [] parseArray(String t){
		
		
		String [] data = t.split("\n");
		
		ComplexNumber [] returnArray = new ComplexNumber[data.length];
		for(int i = 0 ; i < data.length ; i++){
			returnArray[i] = ComplexNumber.parse(data[i]);
		}
		return returnArray;
	}
}